package com.yequan.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ZParams;

import java.util.*;

public class ArticleRedis {

    /**
     * 数据结构分析:
     * <p>
     * 文章结构:
     * 标题:title
     * 连接:link
     * 作者:publisher
     * 发布时间:now
     * 票数:votes
     * <p>
     * 文章存储结构
     * 1)根据发布时间进行排序的:使用有序集合zset:
     * 键(key)-->time:  分值(score)-->发布时间  成员(member)-->article:[文章id]
     * 2)根据文章评分进行排序的:使用有序集合zset:
     * 键(key)-->score:  分值(score)-->投票数  成员(member)-->article:[文章id]
     * <p>
     * 文章id结构:
     * 每发布一篇文章就生成一个文章id,文章id使用string的自增产生string:(本质是一个计数器)
     * 键(key)-->article:
     */

    /**
     * 群组前缀
     */
    private static final String GROUP = "group:";
    /**
     * 生成文章id的键
     */
    private static final String ARTICLE = "article:";
    /**
     * 文章存储结构:根据时间排序的有序集合键
     */
    private static final String ARTICLE_TIME = "time:";
    /**
     * 文章存储结构:根据评分排序的有序集合键
     */
    private static final String ARTICLE_SCORE = "score:";
    /**
     * 已投票集合的键的前部分
     */
    private static final String VOTED = "voted:";

    /**
     * 一周的总秒数
     */
    private static final int ONE_WEEK_IN_SECONDS = 7 * 86400;

    /**
     * 每获取一个投票增加的分数
     * 这里为432的原因:一天的描述/文章展示所需的票数(200)--> 86400/200=432
     */
    private static final int VOTE_SCORE = 432;

    /**
     * 分页展示文章数10
     */
    private static final int ARTICLE_PER_PAGE = 10;

    /**
     * 发布文章
     *
     * @param conn
     * @param articleItem
     */
    public String publishArticle(Jedis conn, Article articleItem) {
        //1.生成文章id
        String articleId = conn.incr(ARTICLE).toString();

        //2.将文章作者或发布人加入到已投票集合中
        conn.sadd(VOTED + articleId, articleItem.getPublisher());
        conn.expire(VOTED + articleId, ONE_WEEK_IN_SECONDS);

        String article = ARTICLE + articleId;
        Map<String, String> articleData = new HashMap<String, String>();
        articleData.put("title", articleItem.getTitle());
        articleData.put("publisher", articleItem.getPublisher());
        articleData.put("link", articleItem.getLink());
        articleData.put("time", String.valueOf(articleItem.getTime()));
        articleData.put("votes", "1");

        //3.将文章信息存入文章的hash中
        conn.hmset(article, articleData);
        //4.给根据分数排序的有序集合设置值
        conn.zadd(ARTICLE_SCORE, articleItem.getTime() + VOTE_SCORE, article);
        //5.给根据时间排序的有序集合设置值
        conn.zadd(ARTICLE_TIME, articleItem.getTime(), article);
        return articleId;
    }

    /**
     * 给文章投票
     *
     * @param conn
     * @param voter
     * @param article
     */
    public void voteArticle(Jedis conn, String voter, String article) {
        //1.判断该文章是否已经在投票期内
        Double publishTime = conn.zscore(ARTICLE_TIME, article);
        long current = System.currentTimeMillis() / 1000;
        if (publishTime + ONE_WEEK_IN_SECONDS < current) {
            //已过投票期
            return;
        }
        String articleId = article.substring(article.indexOf(":") + 1);
        //2.给投票人集合增加投票人
        Long voteNum = conn.sadd(VOTED + articleId, voter);
        if (voteNum == 1) {//投票成功
            //3.给根据分数排序的有序集合增加分数
            conn.zincrby(ARTICLE_SCORE, VOTE_SCORE, article);
            //4.给文章信息hash集合中的票数增加一票
            conn.hincrBy(article, "votes", 1);
        }
    }

    /**
     * 获取票数
     *
     * @param conn
     * @param article
     */
    public Integer getArticleVotes(Jedis conn, String article) {
        return Integer.parseInt(conn.hget(article, "votes"));
    }

    /**
     * 从score:或者time:有序集合中获取文章集合
     *
     * @param conn
     * @param page
     * @param order
     * @return
     */
    public List<Map<String, String>> getArticles(Jedis conn, int page, String order) {
        int start = (page - 1) * ARTICLE_PER_PAGE;
        int end = start + ARTICLE_PER_PAGE - 1;

        Set<String> articles = conn.zrevrange(order, start, end);

        List<Map<String, String>> articleDataList = new ArrayList<Map<String, String>>();
        for (String article : articles) {
            Map<String, String> articleData = conn.hgetAll(article);
            articleData.put("id", article.substring(article.lastIndexOf(":") + 1));
            articleDataList.add(articleData);
        }
        return articleDataList;
    }

    /**
     * 根据投票分数高低获取文章列表
     *
     * @param conn
     * @param page
     * @return
     */
    public List<Map<String, String>> getArticlesByScore(Jedis conn, int page) {
        return getArticles(conn, page, ARTICLE_SCORE);
    }

    /**
     * 根据发布时间获取文章列表
     *
     * @param conn
     * @param page
     * @return
     */
    public List<Map<String, String>> getArticlesByTime(Jedis conn, int page) {
        return getArticles(conn, page, ARTICLE_TIME);
    }

    /**
     * 将一篇文章添加进群组中,一篇文章可以属于一个群组,也可以属于多个群组,所以是String[]
     *
     * @param conn
     * @param articleId
     * @param groups
     */
    public void addGroups(Jedis conn, String articleId, String[] groups) {
        String article = ARTICLE + articleId;
        for (String group : groups) {
            conn.sadd(GROUP + group, article);
        }
    }

    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page) {
        return getGroupArticles(conn, group, page, "score:");
    }

    public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) {
        String key = order + group;
        if (!conn.exists(key)) {
            ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
            conn.zinterstore(key, params, "group:" + group, order);
            conn.expire(key, 60);
        }
        return getArticles(conn, page, key);
    }

}
